#pragma once

#include <vector>
//#include <memory>
#include <stdexcept>
#include <windows.h>
#include "../common/WindbgExt.h"

/// Class to hold and manipulate byte array
/** This class is used to manipulate variable length input and output parameters.
 * It has many type accessors with similar names:
 *  - type <type>_At(offset) - get type value at specified BYTE offset in byte array
 *  - type <type>_Array(offset) - get type value at specified index in type array (all types except Str and WStr)
 *  - <type>_To(offset, type) - set type value to specified BYTE offset in byte array
 *  - <type>_ArrayTo(offset, type) - set type value to specified index in type array (all types except Str and WStr)
 *  - <type>_Resize(count) - resize to sizeof(type) * count bytes (structures only)
 *
 * Example usage:
 * \code{.pl}
 * my $ba2 = new windbg::ByteArray();
 * $advanced->GetSourceFileInformation(10, "test.cpp", 10, 10, $ba2);
 * print "file info: ", $ba2->Str_At(0), "\n";
 * \endcode
 */
class ByteArray
{
private:
	template<typename T>
	void CheckRemainderSize(DWORD i)
	{
		if (Size() < (i + sizeof(T)))
			throw std::out_of_range("byte array is too small");
	}

public:
	ByteArray()
	{
	}

	ByteArray(void* data, DWORD size)
	{
		if (size > 0)
		{
			Resize(size);
			memcpy(&m_data.front(), data, size);
		}
	}

	~ByteArray()
	{
	};

	/// \name Common methods
	//@{
	DWORD Size()
	{
		size_t curSize = m_data.size();
		if (curSize < 2)
			return 0;
		return static_cast<DWORD>(curSize - 2);
	}

	void Resize(DWORD newSize)
	{
		if (newSize == 0)
		{
			m_data.resize(0);
			return;
		}

		m_data.resize(newSize + 2);
		if (newSize > 0)
		{
			// to avoid too long string reading add trailing zero
			m_data[newSize] = 0;
			m_data[newSize + 1] = 0;
		}
	}

	BYTE* GetBuffer()
	{
		if (m_data.empty())
			return NULL;
		return &m_data.front();
	}
	//@}

	/// \name Simple type accessors
	//@{
#define DEFINE_SIMPLE_TYPE_ACCESSORS(Type) \
	Type  Type##_At(DWORD i) \
	{ \
		CheckRemainderSize<Type>(i); \
		return *(reinterpret_cast<Type*>(&m_data[i])); \
	} \
	Type Type##_Array(DWORD index) \
	{ \
		const DWORD offset = index * sizeof(Type); \
		CheckRemainderSize<Type>(offset); \
		return *(reinterpret_cast<Type*>(&m_data[offset])); \
	} \
	void Type##_To(DWORD i, const Type val) \
	{ \
		CheckRemainderSize<Type>(i); \
		*(reinterpret_cast<Type*>(&m_data[i])) = val; \
	} \
	void Type##_ArrayTo(DWORD index, const Type val) \
	{ \
		const DWORD offset = index * sizeof(Type); \
		CheckRemainderSize<Type>(offset); \
		*(reinterpret_cast<Type*>(&m_data[offset])) = val; \
	}

	DEFINE_SIMPLE_TYPE_ACCESSORS(BYTE);
	DEFINE_SIMPLE_TYPE_ACCESSORS(CHAR);
	DEFINE_SIMPLE_TYPE_ACCESSORS(WORD);
	DEFINE_SIMPLE_TYPE_ACCESSORS(SHORT);
	DEFINE_SIMPLE_TYPE_ACCESSORS(DWORD);
	DEFINE_SIMPLE_TYPE_ACCESSORS(LONG);
	DEFINE_SIMPLE_TYPE_ACCESSORS(ULONG64);
	DEFINE_SIMPLE_TYPE_ACCESSORS(LONG64);
	DEFINE_SIMPLE_TYPE_ACCESSORS(FLOAT);
	DEFINE_SIMPLE_TYPE_ACCESSORS(DOUBLE);
	//@}

	/// \name String accessors
	//@{
	char* Str_At(DWORD i)
	{
		CheckRemainderSize<char>(i);
		return reinterpret_cast<char*>(&m_data[i]);
	}

	wchar_t* WStr_At(DWORD i)
	{
		CheckRemainderSize<wchar_t>(i);
		return reinterpret_cast<wchar_t*>(&m_data[i]);
	}

	void Str_To(DWORD i, const char* val)
	{
		if ((!val) || (*val == 0))
		{
			CheckRemainderSize<char>(i);
			m_data[i] = 0;
			return;
		}

		const DWORD strSize = static_cast<DWORD>(strlen(val));
		if (Size() < (i + strSize + 1))
			throw std::out_of_range("byte array is too small");
		memcpy(&m_data[i], val, strSize + 1);
	}

	void WStr_To(DWORD i, const wchar_t* val)
	{
		if ((!val) || (*val == 0))
		{
			CheckRemainderSize<wchar_t>(i);
			m_data[i] = 0;
			m_data[i + 1] = 0;
			return;
		}

		const DWORD strSize = static_cast<DWORD>(wcslen(val)) * sizeof(wchar_t);
		if (Size() < (i + strSize + sizeof(wchar_t)))
			throw std::out_of_range("byte array is too small");
		memcpy(&m_data[i], val, strSize + sizeof(wchar_t));
	}
	//@}

	/// \name Specific WinDBG types accessors
	//@{
#define DEFINE_TYPE_ACCESSORS(Type) \
	void Type##_Resize(DWORD count) \
	{ \
		Resize(count * sizeof(Type)); \
	} \
	Type*  Type##_At(DWORD i) \
	{ \
		return reinterpret_cast<Type*>(&m_data[i]); \
	} \
	Type* Type##_Array(DWORD index) \
	{ \
		return reinterpret_cast<Type*>(&m_data[index * sizeof(Type)]); \
	} \
	void Type##_To(DWORD i, const Type* val) \
	{ \
		CheckRemainderSize<Type>(i); \
		memcpy(&m_data[i], val, sizeof(Type)); \
	} \
	void Type##_ArrayTo(DWORD index, const Type* val) \
	{ \
		const DWORD offset = index * sizeof(Type); \
		CheckRemainderSize<Type>(offset); \
		memcpy(&m_data[offset], val, sizeof(Type)); \
	}

	DEFINE_TYPE_ACCESSORS(DEBUG_SPECIFIC_FILTER_PARAMETERS);
	DEFINE_TYPE_ACCESSORS(DEBUG_EXCEPTION_FILTER_PARAMETERS);
	DEFINE_TYPE_ACCESSORS(DEBUG_STACK_FRAME);
	DEFINE_TYPE_ACCESSORS(DEBUG_STACK_FRAME_EX);
	DEFINE_TYPE_ACCESSORS(INLINE_FRAME_CONTEXT);
	DEFINE_TYPE_ACCESSORS(STACK_SYM_FRAME_INFO);
	DEFINE_TYPE_ACCESSORS(DEBUG_VALUE);
	DEFINE_TYPE_ACCESSORS(DEBUG_MODULE_PARAMETERS);
	DEFINE_TYPE_ACCESSORS(DEBUG_MODULE_AND_ID);
	DEFINE_TYPE_ACCESSORS(DEBUG_OFFSET_REGION);
	DEFINE_TYPE_ACCESSORS(DEBUG_SYMBOL_SOURCE_ENTRY);
	DEFINE_TYPE_ACCESSORS(DEBUG_SYMBOL_PARAMETERS);
	DEFINE_TYPE_ACCESSORS(DEBUG_READ_USER_MINIDUMP_STREAM);
	DEFINE_TYPE_ACCESSORS(DEBUG_GET_TEXT_COMPLETIONS_IN);
	DEFINE_TYPE_ACCESSORS(DEBUG_GET_TEXT_COMPLETIONS_OUT);
	DEFINE_TYPE_ACCESSORS(DEBUG_CACHED_SYMBOL_INFO);
	DEFINE_TYPE_ACCESSORS(DEBUG_THREAD_BASIC_INFORMATION);
	DEFINE_TYPE_ACCESSORS(DEBUG_BREAKPOINT_PARAMETERS);
	DEFINE_TYPE_ACCESSORS(DEBUG_CREATE_PROCESS_OPTIONS);
	DEFINE_TYPE_ACCESSORS(DEBUG_LAST_EVENT_INFO_BREAKPOINT);
	DEFINE_TYPE_ACCESSORS(DEBUG_LAST_EVENT_INFO_EXCEPTION);
	DEFINE_TYPE_ACCESSORS(DEBUG_LAST_EVENT_INFO_EXIT_THREAD);
	DEFINE_TYPE_ACCESSORS(DEBUG_LAST_EVENT_INFO_EXIT_PROCESS);
	DEFINE_TYPE_ACCESSORS(DEBUG_LAST_EVENT_INFO_LOAD_MODULE);
	DEFINE_TYPE_ACCESSORS(DEBUG_LAST_EVENT_INFO_UNLOAD_MODULE);
	DEFINE_TYPE_ACCESSORS(DEBUG_LAST_EVENT_INFO_SYSTEM_ERROR);
	DEFINE_TYPE_ACCESSORS(DEBUG_PROCESSOR_IDENTIFICATION_ALPHA);
	DEFINE_TYPE_ACCESSORS(DEBUG_PROCESSOR_IDENTIFICATION_AMD64);
	DEFINE_TYPE_ACCESSORS(DEBUG_PROCESSOR_IDENTIFICATION_IA64);
	DEFINE_TYPE_ACCESSORS(DEBUG_PROCESSOR_IDENTIFICATION_X86);
	DEFINE_TYPE_ACCESSORS(DEBUG_PROCESSOR_IDENTIFICATION_ARM);
	DEFINE_TYPE_ACCESSORS(DEBUG_PROCESSOR_IDENTIFICATION_ALL);
	DEFINE_TYPE_ACCESSORS(DEBUG_REGISTER_DESCRIPTION);
	DEFINE_TYPE_ACCESSORS(DEBUG_SYMBOL_ENTRY);
	//@}

private:
	std::vector<BYTE> m_data;
};

